iT邦幫忙

2024 iThome 鐵人賽

DAY 22
0
Software Development

深入淺出Java 30天系列 第 22

Day 22: 設計並記錄繼承的使用方式,否則禁止使用繼承

  • 分享至 

  • xImage
  •  

在前兩天的時候有提過,盡量使用composition,而不是繼承,但如果真得需要使用繼承,需要做哪些事呢?

必須撰寫文件和說明紀錄

需要說明可以覆寫的方法(publicprotected的方法)和constructor,會呼叫哪些方法和回傳的結果,以及結果會對caller造成什麼影響。寫出會呼叫哪些方法,可以在該方法有異動的時候,可以確認行為是不是有變動。例如前天的範例,HashSetaddAll應該在文件告知有呼叫add

設計被繼承的類別,要盡可能提供最少可覆寫的方法

但是如果提供太少可覆寫的方法,又會失去繼承的意義,所以需要明智地選擇要提供哪些protected的方法,既可提供繼承,又不會像public的方法被過多類別使用,影響範圍過大。

java.util.AbstractListprotected void removeRange(int fromIndex, int toIndex)為例,提供子類別可以改寫這個方法,更彈性的移除list的item,不必花費時間設計如何使用clear()移除sublist。

要測試會被繼承的類別,最好的測試方式是繼承它

並使用繼承它的子類別,來測試繼承之後的行為。

要被繼承的類別,constructor不能呼叫會被覆寫的方法

因為子類別被實體化的時候,會先呼叫父類別的constructor,如果被覆寫的方法有使用只有子類別才有的欄位或方法,會出現不預期的行為。
例如下面的範例,子類別的doSomething()用了父類別沒有的欄位,結果Child()實體化的過程中,先呼叫Parent()的constructor,Parent()呼叫了doSomething(),但doSomething()已經被子類別覆寫,而且在還沒呼叫子類別constructor的情況下,存取了父類別沒有的欄位childField,因為childField還沒有被給值,結果就拿到null

class Parent {
    public Parent() {
        // Constructor of Parent class
        System.out.println("Parent constructor called");
        doSomething();
    }

    protected void doSomething() {
        System.out.println("Parent doSomething called");
    }
}

public class Child extends Parent {
    private String childField;

    public Child() {
        // Constructor of Child class
        this.childField = "Child specific field";
        System.out.println("Child constructor called");
    }

    @Override
    protected void doSomething() {
        System.out.println("Child doSomething called");
        System.out.println("Child field value: " + childField); // Potential problem
    }

    public static void main(String[] args) {
        Child child = new Child();
        child.doSomething(); // Child doSomething called
    }
}


上面的範例,很好的示範實體化時,若父類別的方法比子類別更早被呼叫,且使用了被覆寫的方法,出現的不預期行為,所以實作CloneableSerializable的類別需特別注意,如果實作這兩個interface的類別有子類別,子類別有覆寫clonereadObject,要小心有沒有使用父類別沒有的欄位和方法。

從上面的說明可以知道,設計一個可以被繼承的類別,必須做很多事確保子類別不會濫用這個功能,所以類別如果沒有很好的說明文件,還是不要輕易被繼承,可以把constructor設為private,改用static factories,就可以防止被繼承,並改用前兩天介紹的composition去實現擴增功能的需求。


上一篇
Day 21: 最好使用composition而不是繼承(下)
下一篇
Day 23: 偏好使用interface而不是抽象類別(abstract class)(上)
系列文
深入淺出Java 30天30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言